package com.wxy.favorites.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.google.common.collect.Lists;
import com.wxy.favorites.config.AppConfig;
import com.wxy.favorites.constant.ErrorConstants;
import com.wxy.favorites.constant.PublicConstants;
import com.wxy.favorites.core.BizException;
import com.wxy.favorites.core.NoRollbackException;
import com.wxy.favorites.core.PageInfo;
import com.wxy.favorites.dao.*;
import com.wxy.favorites.dto.FavoritesMoveDto;
import com.wxy.favorites.dto.FavoritesTemplateDto;
import com.wxy.favorites.dto.SearchDto;
import com.wxy.favorites.entity.*;
import com.wxy.favorites.security.ContextUtils;
import com.wxy.favorites.security.SecurityUser;
import com.wxy.favorites.util.AssertUtils;
import com.wxy.favorites.util.HtmlUtils;
import com.wxy.favorites.util.PinYinUtils;
import com.wxy.favorites.util.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.criteria.Predicate;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Author wangxiaoyuan
 * @Date 2020/4/24 11:50
 * @Description
 **/
@Service
@Slf4j
@Transactional(noRollbackFor = NoRollbackException.class)
public class FavoritesService {

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private FavoritesRepository favoritesRepository;

    @Autowired
    private MemorandumRepository memorandumRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    @Autowired
    private MomentRepository momentRepository;

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordService passwordService;

    @Autowired
    private SearchTypeRepository searchTypeRepository;

    @Autowired
    private TaskService taskService;

    @Autowired
    private MomentService momentService;
    @Autowired
    private QuickNavigationRepository quickNavigationRepository;

    public void deleteAll(List<Favorites> list) {
        favoritesRepository.deleteAll(list);
    }

    public List<Favorites> findByCategoryId(Integer categoryId) {
        Sort sort = Sort.by(Sort.Order.desc("sort"), Sort.Order.asc("id"));
        return favoritesRepository.findByCategoryIdAndDeleteFlag(categoryId, 0, sort);
    }

    public Favorites save(Favorites favorites) {
        return favoritesRepository.save(favorites);
    }

    public List<Favorites> findLimitByCategoryId(Integer categoryId) {
        Sort sort = Sort.by(Sort.Order.desc("sort"), Sort.Order.asc("id"));
        Pageable pageable = PageRequest.of(0, appConfig.getFavoritesLimit(), sort);
        return favoritesRepository.findLimitByCategoryIdAndDeleteFlag(categoryId, 0, pageable);
    }

    public void deleteById(Integer id) {
        favoritesRepository.deleteById(id);
    }

    public Favorites findById(Integer id) {
        return favoritesRepository.findById(id).orElse(null);
    }

    public Favorites findByShortcut(String shortcut, Integer userId) {
        return favoritesRepository.findByShortcutAndUserIdAndDeleteFlag(shortcut, userId, 0);
    }

    public List<Favorites> findFavorites(Integer userId, String searchName) {
        String text = SqlUtils.trimAndEscape(searchName);
        Pageable pageable = PageRequest.of(0, appConfig.getFavoritesSearchLimit());
        // 构造自定义查询条件
        Specification<Favorites> queryCondition = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            predicateList.add(criteriaBuilder.equal(root.get("userId"), userId));
            predicateList.add(criteriaBuilder.equal(root.get("deleteFlag"), 0));
            if (StrUtil.isNotBlank(text)) {
                predicateList.add(criteriaBuilder.or(criteriaBuilder.like(root.get("name"), "%" + text + "%"), criteriaBuilder.like(root.get("pinyin"), "%" + text + "%"), criteriaBuilder.like(root.get("pinyinS"), "%" + text + "%")));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        return favoritesRepository.findAll(queryCondition, pageable).getContent();
    }

    public List<Favorites> findStarFavorites(Integer userId) {
        return favoritesRepository.findStarFavorites(userId);
    }

    public PageInfo<Favorites> findRecycleByPage(Integer userId, Integer pageNum, Integer pageSize) {
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "deleteTime"));
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        Page<Favorites> page = favoritesRepository.findByUserIdAndDeleteFlag(userId, PublicConstants.DELETE_CODE, pageable);
        return new PageInfo<>(page.getContent(), page.getTotalPages(), page.getTotalElements());
    }

    public PageInfo<Favorites> findShareByPage(Integer userId, Integer pageNum, Integer pageSize) {
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "support"));
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        Page<Favorites> page = favoritesRepository.findByUserIdAndIsShare(userId, PublicConstants.SHARE_CODE, pageable);
        return new PageInfo<>(page.getContent(), page.getTotalPages(), page.getTotalElements());
    }

    public void deleteAllFromRecycle(Integer userId) {
        Favorites favorites = new Favorites();
        favorites.setUserId(userId);
        favorites.setDeleteFlag(1);
        List<Favorites> all = favoritesRepository.findAll(Example.of(favorites));
        favoritesRepository.deleteAll(all);
    }

    public void deleteAllFromRecycleWithBeforeTime(String time) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
        favoritesRepository.deleteByDeleteFlagAndDeleteTimeBefore(PublicConstants.DELETE_CODE, sdf.parse(time));
    }

    public void updateShare(Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElseThrow(() -> new BizException(ErrorConstants.ILLEGAL_OPERATION_MSG));
        favorites.setIsShare(null);
        favoritesRepository.save(favorites);
    }

    public PageInfo<Favorites> findShareList(String name, Integer pageNum, Integer pageSize, Integer sortType) {
        name = Optional.ofNullable(name).orElse("").toLowerCase();
        name = SqlUtils.trimAndEscape(name);
        List<Sort.Order> orders = new ArrayList<>();
        if (Objects.equals(sortType, 1)) {
            orders.add(new Sort.Order(Sort.Direction.DESC, "clickCount"));
        } else if (Objects.equals(sortType, 2)) {
            orders.add(new Sort.Order(Sort.Direction.DESC, "support"));
        }
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        Page<Favorites> page = favoritesRepository.findShareList("%" + name + "%", pageable);
        return new PageInfo<>(page.getContent(), page.getTotalPages(), page.getTotalElements());

    }

    public Integer saveSupport(Integer userId, Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElseThrow(() -> new BizException(ErrorConstants.ILLEGAL_OPERATION_MSG));
        if (!Objects.equals(favorites.getUserId(), userId)) {
            String name = PublicConstants.SHARE_CATEGORY_NAME;
            Category category = categoryRepository.findByNameAndUserIdAndIsSystem(name, userId, PublicConstants.SPECIAL_CATEGORY_CODE);
            if (category == null) {
                category = new Category().setIsSystem(PublicConstants.SPECIAL_CATEGORY_CODE).setName(name).setPinyin(PinYinUtils.toPinyin(name)).setPinyinS(name)
                        .setSort(PublicConstants.MAX_SORT_NUMBER).setUserId(userId);
                categoryRepository.save(category);
            }
            Favorites favorites2 = new Favorites();
            favorites2.setName(favorites.getName());
            favorites2.setIcon(favorites.getIcon());
            favorites2.setUrl(favorites.getUrl());
            favorites2.setCategoryId(category.getId());
            favorites2.setUserId(userId);
            favorites2.setPinyin(favorites.getPinyin());
            favorites2.setPinyinS(favorites.getPinyinS());
            favoritesRepository.save(favorites2);
            // support + 1
            favorites.setSupport(Optional.ofNullable(favorites.getSupport()).orElse(0) + 1);
            favoritesRepository.save(favorites);
        }
        return favoritesRepository.findById(id).map(Favorites::getSupport).orElse(0);
    }

    public void cleanShare(Integer userId) {
        favoritesRepository.cleanShare(userId);
    }

    public Favorites update(Favorites favorites) {
        return favoritesRepository.save(favorites);
    }

    public void recoverAll(Integer userId) {
        favoritesRepository.recoverAll(userId);
    }

    public PageInfo<Favorites> findPageByCategoryId(Integer categoryId, String name, Integer pageNum, Integer pageSize) {
        if (categoryId == null) {
            Category category = categoryRepository.findByNameAndUserIdAndIsSystem(PublicConstants.DEFAULT_CATEGORY_NAME, ContextUtils.getCurrentUser().getId(), PublicConstants.SYSTEM_CATEGORY_CODE);
            categoryId = category.getId();
        }
        String text = SqlUtils.trimAndEscape(name);
        // 构造自定义查询条件
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(orders));
        Integer finalCategoryId = categoryId;
        Specification<Favorites> queryCondition = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            predicateList.add(criteriaBuilder.equal(root.get("categoryId"), finalCategoryId));
            predicateList.add(criteriaBuilder.equal(root.get("deleteFlag"), 0));
            if (StrUtil.isNotBlank(text)) {
                predicateList.add(criteriaBuilder.or(criteriaBuilder.like(root.get("name"), "%" + text + "%"), criteriaBuilder.like(root.get("pinyin"), "%" + text + "%"), criteriaBuilder.like(root.get("pinyinS"), "%" + text + "%")));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        Page<Favorites> page = favoritesRepository.findAll(queryCondition, pageable);
        return new PageInfo<>(page.getContent(), page.getTotalPages(), page.getTotalElements());
    }

    public void move(FavoritesMoveDto dto) {
        AssertUtils.isTrue(CollUtil.isNotEmpty(dto.getIds()) || (dto.getFrom() != null && Objects.equals(dto.getMoveAll(), 1)), "请选择书签");
        Category category = categoryRepository.findById(dto.getTo()).orElseThrow(() -> new BizException("分类不存在"));
        List<Favorites> list = Objects.equals(dto.getMoveAll(), 1)
                ? favoritesRepository.findByCategoryIdAndDeleteFlag(dto.getFrom(), 0)
                : favoritesRepository.findAllById(dto.getIds());
        list.forEach(f -> f.setCategoryId(category.getId()));
        favoritesRepository.saveAll(list);
    }

    public void recoverOne(Integer id) {
        favoritesRepository.recoverOne(id);
    }

    public Integer setTop(Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElseThrow(() -> new BizException("收藏不存在"));
        Integer max = Optional.ofNullable(favoritesRepository.findMaxSortByCategoryId(favorites.getCategoryId())).orElse(0);
        favorites.setSort(max < PublicConstants.MAX_SORT_NUMBER ? max + 1 : PublicConstants.MAX_SORT_NUMBER);
        favoritesRepository.save(favorites);
        return favorites.getSort();
    }

    public Integer clickCountAdd(Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElseThrow(() -> new BizException("收藏不存在"));
        AssertUtils.notNull(favorites, "书签不存在");
        favorites.setClickCount(favorites.getClickCount() + 1);
        favoritesRepository.save(favorites);
        return favoritesRepository.findById(id).map(Favorites::getClickCount).orElse(0);
    }

    public void saveLink(Favorites favorites) {
        SecurityUser user = ContextUtils.getCurrentUser();
        favorites.setUserId(user.getId());
        Category category = categoryService.findLinkCategory(user.getId());
        favorites.setCategoryId(category.getId());
        favorites.setIsShare(1);
        // 处理图标
        String icon = HtmlUtils.getIcon(favorites.getUrl());
        favorites.setIcon(StrUtil.isBlank(icon) ? PublicConstants.FAVORITES_ICON_DEFAULT : icon);
        // 拼音
        favorites.setPinyin(PinYinUtils.toPinyin(favorites.getName()));
        // 拼音首字母
        favorites.setPinyinS(PinYinUtils.toPinyinS(favorites.getName()));
        favoritesRepository.save(favorites);
    }

    public void saveFavorites(Favorites favorites) {
        AssertUtils.notNull(favorites.getCategoryId(), "分类不能为空");
        SecurityUser user = ContextUtils.getCurrentUser();
        favorites.setUserId(user.getId());
        // 处理图标
        String icon = HtmlUtils.getIcon(favorites.getUrl());
        favorites.setIcon(StrUtil.isBlank(icon) ? PublicConstants.FAVORITES_ICON_DEFAULT : icon);
        // 拼音
        favorites.setPinyin(PinYinUtils.toPinyin(favorites.getName()));
        // 拼音首字母
        favorites.setPinyinS(PinYinUtils.toPinyinS(favorites.getName()));
        favoritesRepository.save(favorites);
    }

    public void updateFavorites(Favorites favorites) {
        Favorites favorites1 = favoritesRepository.findById(favorites.getId()).orElse(null);
        AssertUtils.notNull(favorites1, "书签不存在");
        favorites1.setUrl(favorites.getUrl());
        favorites1.setName(favorites.getName());
        // 处理图标
        String icon = HtmlUtils.getIcon(favorites.getUrl());
        favorites1.setIcon(StrUtil.isBlank(icon) ? PublicConstants.FAVORITES_ICON_DEFAULT : icon);
        // 拼音
        favorites1.setPinyin(PinYinUtils.toPinyin(favorites.getName()));
        // 拼音首字母
        favorites1.setPinyinS(PinYinUtils.toPinyinS(favorites.getName()));
        favorites1.setCategoryId(favorites.getCategoryId());
        favorites1.setStar(favorites.getStar());
        favorites1.setSchemaName(favorites.getSchemaName());
        favorites1.setShortcut(favorites.getShortcut());
        favorites1.setIsShare(favorites.getIsShare());
        favoritesRepository.save(favorites1);
    }

    public PageInfo<Category> findListPage(Integer pageNum, Integer pageSize) {
        SecurityUser user = ContextUtils.getCurrentUser();
        // 查询用户分类
        PageInfo<Category> page = categoryService.findPageByUserId(user.getId(), pageNum, pageSize);
        for (Category c : page.getList()) {
            c.setFavorites(findLimitByCategoryId(c.getId()));
        }
        return page;
    }

    public Category position(Integer categoryId) {
        Category category = categoryRepository.findById(categoryId).orElse(null);
        AssertUtils.notNull(category, "分类不存在");
        category.setFavorites(findLimitByCategoryId(category.getId()));
        return category;
    }

    public void delete(Integer id) {
        favoritesRepository.findById(id).ifPresent(f -> {
            f.setDeleteTime(new Date());
            f.setDeleteFlag(PublicConstants.DELETE_CODE);
            favoritesRepository.save(f);
        });
    }

    public Favorites query(Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElse(null);
        Optional.ofNullable(favorites).ifPresent(f -> Optional.ofNullable(categoryService.findById(favorites.getCategoryId())).ifPresent(c -> f.setCategoryName(c.getName())));
        return favorites;
    }

    public SearchDto search(String name) {
        SecurityUser user = ContextUtils.getCurrentUser();
        name = Optional.ofNullable(name).orElse("").trim().toLowerCase();// 转换小写搜索
        List<Favorites> favoritesList = findFavorites(user.getId(), name);
        List<Category> categoryList = categoryService.findCategories(user.getId(), name);
        return new SearchDto().setFavoritesList(favoritesList).setCategoryList(categoryList);
    }

    public void visit(Integer id) {
        Favorites favorites = favoritesRepository.findById(id).orElse(null);
        AssertUtils.notNull(favorites, "书签不存在");
        SecurityUser securityUser = ContextUtils.getCurrentUser();
        User user = userRepository.findById(securityUser.getId()).orElse(null);
        AssertUtils.notNull(user, "用户不存在");
        favorites.setVisitTime(new Date());
        favorites.setClickCount(favorites.getClickCount() + 1);
        favoritesRepository.save(favorites);
        user.setClickCount(Optional.ofNullable(user.getClickCount()).orElse(0) + 1);
        userRepository.save(user);
    }

    public void star(Favorites favorites) {
        Favorites favorites1 = favoritesRepository.findById(favorites.getId()).orElse(null);
        AssertUtils.notNull(favorites1, ErrorConstants.ILLEGAL_OPERATION_MSG);
        if (favorites.getStar() == 1) {
            SecurityUser user = ContextUtils.getCurrentUser();
            List<Favorites> list = findStarFavorites(user.getId());
            AssertUtils.isFalse(list.size() >= appConfig.getStarLimit() && !list.contains(favorites1), PublicConstants.FAVORITES_STAR_LIMITED_MSG);
        }
        favorites1.setStar(favorites.getStar());
        favoritesRepository.save(favorites1);
    }

    public void share(Favorites favorites) {
        Favorites favorites1 = favoritesRepository.findById(favorites.getId()).orElse(null);
        AssertUtils.notNull(favorites1, ErrorConstants.ILLEGAL_OPERATION_MSG);
        favorites1.setIsShare(favorites.getIsShare());
        favoritesRepository.save(favorites1);
    }

    public void upload(MultipartFile file) throws IOException {
        AssertUtils.isTrue(file.getSize() > 0 && Optional.ofNullable(file.getOriginalFilename()).orElse("").endsWith(".xml"), "文件格式不正确");
        SecurityUser user = ContextUtils.getCurrentUser();
        List<Category> list = parseCategoryList(file.getInputStream(), user.getId());
        // 查询用户已存在的数据，防止重复导入
        List<Category> categories = categoryService.findByUserId(user.getId());
        categories.forEach(category -> {
            List<Favorites> favoritesList = findByCategoryId(category.getId());
            favoritesList.forEach(favorites -> {
                Password password = passwordService.findByFavoritesId(favorites.getId());
                favorites.setPassword(password);
            });
            category.setFavorites(favoritesList);
        });
        // 遍历导入数据
        list.forEach(c -> {
            Category category = existCategory(c.getName(), categories);
            if (category == null) {// 如果该分类不存在，则新增分类，并保存所有收藏
                c.setUserId(user.getId());
                categoryService.save(c);
                // 保存所有收藏
                Optional.ofNullable(c.getFavorites()).orElse(Collections.emptyList()).forEach(f -> {
                    f.setCategoryId(c.getId());
                    f.setUserId(user.getId());
                    Favorites favorites = favoritesRepository.save(f);
                    if (f.getPassword() != null) {
                        Password password = f.getPassword();
                        password.setUserId(user.getId());
                        password.setFavoritesId(favorites.getId());
                        passwordService.save(password);
                    }
                });
            } else {// 如果该分类存在，则跳过分类，直接遍历收藏
                Optional.ofNullable(c.getFavorites()).orElse(Collections.emptyList()).forEach(f -> {
                    Favorites favorites = existFavorites(f.getUrl(), category.getFavorites());
                    if (favorites == null) {// 如果收藏不存在，则保存收藏
                        f.setCategoryId(category.getId());
                        f.setUserId(user.getId());
                        Favorites save = favoritesRepository.save(f);
                        // 保存密码
                        Password password = f.getPassword();
                        if (password != null) {
                            password.setUserId(user.getId());
                            password.setFavoritesId(save.getId());
                            passwordService.save(password);
                        }
                    } else {// 如果收藏存在，则跳过收藏，看是否有密码需要新增
                        Password password = f.getPassword();
                        if (password != null && favorites.getPassword() == null) {
                            password.setUserId(user.getId());
                            password.setFavoritesId(favorites.getId());
                            passwordService.save(password);
                        }
                    }
                });
            }
        });
        // 保存瞬间
        parseMomentList(file.getInputStream());
        // 保存日程
        parseTaskList(file.getInputStream());
        // 保存搜索引擎
        parseSearchTypeList(file.getInputStream());
        // 保存快捷导航
        parseNavigationList(file.getInputStream());
        // 保存备忘录
        parseMemorandumList(file.getInputStream());
    }

    private void parseMomentList(InputStream in) {
        Integer userId = ContextUtils.getCurrentUser().getId();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            if (root.element("MOMENTS") != null) {
                root.element("MOMENTS").elements("MOMENT").forEach(m -> {
                    String content = m.elementText("CONTENT");
                    String text = m.elementText("TEXT");
                    String time = m.elementText("TIME");
                    if (StrUtil.isNotBlank(content) && StrUtil.isNotBlank(time)) {
                        Date time1;
                        try {
                            time1 = sdf.parse(time);
                        } catch (ParseException e) {
                            time1 = new Date();
                        }
                        momentRepository.save(new Moment().setIsTop(0).setContent(content).setText(text).setUserId(userId).setCreateTime(time1));
                    }
                });
            }
        } catch (Exception e) {
            log.error("瞬间导入失败：userId = {}", userId, e);
        }
    }

    private void parseTaskList(InputStream in) {
        Integer userId = ContextUtils.getCurrentUser().getId();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATE_PATTERN);
            SimpleDateFormat sdf1 = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            Date today = sdf.parse(sdf.format(new Date()));
            if (root.element("TASKS") != null) {
                root.element("TASKS").elements("TASK").forEach(t -> {
                    try {
                        Date date = sdf.parse(t.elementText("DATE"));
                        if (date.getTime() >= today.getTime()) {
                            taskRepository.save(new Task().setContent(t.elementText("CONTENT")).setTaskDate(date).setIsAlarm(Boolean.parseBoolean(t.elementText("ALARM")) ? PublicConstants.TASK_ALARM_CODE : 0).setAlarmTime(sdf1.parse(t.elementText("TIME"))).setUserId(userId).setLevel(Integer.valueOf(t.elementText("LEVEL"))));
                        }
                    } catch (ParseException ignored) {
                    }
                });
            }
        } catch (Exception e) {
            log.error("日程导入失败：userId = {}", userId, e);
        }
    }

    private void parseSearchTypeList(InputStream in) {
        Integer userId = ContextUtils.getCurrentUser().getId();
        List<String> names = searchTypeRepository.findByUserId(userId).stream().map(SearchType::getName).toList();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            if (root.element("SEARCH_TYPES") != null) {
                root.element("SEARCH_TYPES").elements("SEARCH_TYPE").forEach(s -> {
                    String name = s.elementText("NAME");
                    if (!names.contains(name)) {
                        searchTypeRepository.save(new SearchType().setName(name).setIcon(s.elementText("ICON")).setUrl(s.elementText("URL")).setUserId(userId));
                    }
                });
            }
        } catch (Exception e) {
            log.error("搜索导入失败：userId = {}", userId, e);
        }
    }

    private void parseNavigationList(InputStream in) {
        Integer userId = ContextUtils.getCurrentUser().getId();
        List<String> urls = quickNavigationRepository.findAllByUserIdOrderBySort(userId).stream().map(QuickNavigation::getUrl).toList();
        try {
            List<QuickNavigation> list = new ArrayList<>();
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            if (root.element("NAVIGATIONS") != null) {
                root.element("NAVIGATIONS").elements("NAVIGATION").forEach(n -> {
                    String url = n.elementText("URL");
                    int sort = isInteger(n.elementText("SORT")) ? Integer.parseInt(n.elementText("SORT")) : -1;
                    if (!urls.contains(url)) {
                        list.add(new QuickNavigation().setName(n.elementText("NAME")).setIcon(n.elementText("ICON")).setUrl(url).setUserId(userId));
                    }
                });
            }
            int max = Math.min(appConfig.getNavigationLimit() - urls.size(), list.size());
            for (int i = 0; i < max; i++) {
                quickNavigationRepository.save(list.get(i));
            }
        } catch (Exception e) {
            log.error("快捷导航导入失败：userId = {}", userId, e);
        }
    }

    private void parseMemorandumList(InputStream in) {
        Integer userId = ContextUtils.getCurrentUser().getId();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            if (root.element("MEMORANDUMS") != null) {
                root.element("MEMORANDUMS").elements("MEMORANDUM").forEach(r -> {
                    String content = r.elementText("CONTENT");
                    String createTime = r.elementText("CREATE_TIME");
                    if (StrUtil.isNotBlank(content) && StrUtil.isNotBlank(createTime)) {
                        Date time;
                        try {
                            time = sdf.parse(createTime);
                        } catch (ParseException e) {
                            time = new Date();
                        }
                        memorandumRepository.save(new Memorandum().setContent(content).setUserId(userId).setCreateTime(time));
                    }
                });
            }
        } catch (Exception e) {
            log.error("备忘录导入失败：userId = {}", userId, e);
        }
    }

    private List<Category> parseCategoryList(InputStream in, Integer userId) {
        List<Category> list = new ArrayList<>();
        try {
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element root = document.getRootElement();
            if (root.element("CATEGORIES") != null) {
                root.element("CATEGORIES").elements("CATEGORY").forEach(c -> {
                    List<Favorites> list1 = new ArrayList<>();
                    c.element("LIST").elements("FAVORITES").forEach(f -> {
                        int sort = isInteger(f.elementText("SORT")) ? Integer.parseInt(f.elementText("SORT")) : -1;
                        Favorites favorites = new Favorites().setName(f.elementText("NAME")).setIcon(f.elementText("ICON")).setUrl(f.elementText("URL")).setPinyin(PinYinUtils.toPinyin(f.elementText("NAME"))).setPinyinS(PinYinUtils.toPinyinS(f.elementText("NAME"))).setShortcut(StrUtil.isNotBlank(f.elementText("SHORTCUT")) ? f.elementText("SHORTCUT") : null).setSchemaName(StrUtil.isNotBlank(f.elementText("SCHEMA_NAME")) ? f.elementText("SCHEMA_NAME") : null).setStar(Boolean.parseBoolean(f.elementText("STAR")) ? PublicConstants.FAVORITES_STAR_CODE : null).setIsShare(Boolean.parseBoolean(f.elementText("SHARE")) ? PublicConstants.SHARE_CODE : null);
                        Element pwd = f.element("USER");
                        if (pwd != null) {
                            Password password = new Password().setAccount(pwd.elementText("ACCOUNT")).setPassword(pwd.elementText("PASSWORD"));
                            favorites.setPassword(password);
                        }
                        list1.add(favorites);
                    });
                    list.add(new Category().setName(c.elementText("NAME")).setBookmark(Boolean.parseBoolean(c.elementText("BOOKMARK")) ? PublicConstants.BOOKMARK_STYLE_CODE : null).setPinyin(PinYinUtils.toPinyin(c.elementText("NAME"))).setPinyinS(PinYinUtils.toPinyinS(c.elementText("NAME"))).setFavorites(list1));
                });
            }
        } catch (Exception e) {
            log.error("收藏导入失败：userId = {}", userId, e);
        }
        return list;
    }

    private boolean isInteger(String str) {
        Pattern pattern = Pattern.compile("^[-+]?[\\d]*$");
        return StrUtil.isNotBlank(str) && pattern.matcher(str).matches();
    }

    private Favorites existFavorites(String url, List<Favorites> favorites) {
        if (favorites != null && favorites.size() > 0) {
            for (Favorites f : favorites) {
                if (f.getUrl().equals(url)) {
                    return f;
                }
            }
        }
        return null;
    }

    private Category existCategory(String name, List<Category> categories) {
        if (categories != null && categories.size() > 0) {
            for (Category c : categories) {
                if (c.getName().equals(name)) {
                    return c;
                }
            }
        }
        return null;
    }

    public void importTemplate(MultipartFile file) throws IOException {
        AssertUtils.isTrue(file.getSize() > 0 && Optional.ofNullable(file.getOriginalFilename()).orElse("").endsWith(".xlsx"), "文件格式不正确");
        SecurityUser user = ContextUtils.getCurrentUser();
        Category category = categoryService.findTemplateCategory(user.getId());
        List<String> existsUrls = findByCategoryId(category.getId()).stream().map(Favorites::getUrl).toList();
        ArrayList<FavoritesTemplateDto> list = new ArrayList<>();
        EasyExcel.read(file.getInputStream(), FavoritesTemplateDto.class, new ReadListener<FavoritesTemplateDto>() {
            @Override
            public void invoke(FavoritesTemplateDto data, AnalysisContext analysisContext) {
                list.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            }
        }).sheet().doRead();
        log.info("导入书签：size = {}", list.size());
        Lists.partition(list, Optional.ofNullable(appConfig.getImportBatchSize()).orElse(100))
                .forEach(subList -> taskExecutor.execute(() -> subList.forEach(dto -> {
                    String url = dto.getUrl();
                    String name = dto.getName();
                    if (StrUtil.isNotBlank(name) && ReUtil.isMatch(PublicConstants.URL_PATTERN, url) && !existsUrls.contains(url)) {
                        String icon = HtmlUtils.getRootIcon(url);
                        Favorites f = new Favorites();
                        f.setName(name);
                        f.setUrl(url);
                        f.setIcon(StrUtil.isNotBlank(icon) ? icon : PublicConstants.FAVORITES_ICON_DEFAULT);
                        f.setUserId(user.getId());
                        f.setPinyin(PinYinUtils.toPinyin(name));
                        f.setPinyinS(PinYinUtils.toPinyinS(name));
                        f.setCategoryId(category.getId());
                        favoritesRepository.save(f);
                    }
                })));
    }

    public void importHtml(MultipartFile file) throws IOException {
        AssertUtils.isTrue(file.getSize() > 0 && Optional.ofNullable(file.getOriginalFilename()).orElse("").endsWith(".html"), "文件格式不正确");
        SecurityUser user = ContextUtils.getCurrentUser();
        Category category = categoryService.findHtmlCategory(user.getId());
        List<String> existsUrls = findByCategoryId(category.getId()).stream().map(Favorites::getUrl).toList();
        org.jsoup.nodes.Document document = Jsoup.parse(file.getInputStream(), StandardCharsets.UTF_8.name(), "");
        Elements elements = document.getElementsByTag("a");
        log.info("导入书签：size = {}", elements.size());
        Lists.partition(elements, Optional.ofNullable(appConfig.getImportBatchSize()).orElse(100))
                .forEach(subList -> taskExecutor.execute(() -> {
                    subList.forEach(element -> {
                        String url = element.attr("href");
                        String name = element.text();
                        if (StrUtil.isNotBlank(name) && ReUtil.isMatch(PublicConstants.URL_PATTERN, url) && !existsUrls.contains(url)) {
                            String icon = HtmlUtils.getRootIcon(url);
                            Favorites f = new Favorites();
                            f.setName(name);
                            f.setUrl(url);
                            f.setIcon(StrUtil.isNotBlank(icon) ? icon : PublicConstants.FAVORITES_ICON_DEFAULT);
                            f.setUserId(user.getId());
                            f.setPinyin(PinYinUtils.toPinyin(name));
                            f.setPinyinS(PinYinUtils.toPinyinS(name));
                            f.setCategoryId(category.getId());
                            favoritesRepository.save(f);
                        }
                    });
                }));
    }

    public void downloadTemplate(OutputStream outputStream) {
        FavoritesTemplateDto dto = new FavoritesTemplateDto();
        dto.setName("示例网址");
        dto.setUrl("http://127.0.0.1/index.html");
        List<FavoritesTemplateDto> list = List.of(dto);
        EasyExcel.write(outputStream, FavoritesTemplateDto.class)
                .sheet("模板")
                .doWrite(list);
    }

    public void exportFavorites(OutputStream out, String favorites, String moment, String task, String navigation, String memorandum, String search) throws IOException {
        List<Category> categories = new ArrayList<>();
        List<Moment> momentList = new ArrayList<>();
        List<Task> taskList = new ArrayList<>();
        List<SearchType> searchTypeList = new ArrayList<>();
        List<QuickNavigation> quickNavigationList = new ArrayList<>();
        List<Memorandum> memorandumList = new ArrayList<>();
        SecurityUser user = ContextUtils.getCurrentUser();
        // 查询用户分类
        if (PublicConstants.EXPORT_FAVORITES_CODE.equals(favorites)) {
            categories = categoryService.findByUserId(user.getId());
            categories.forEach(category -> {
                List<Favorites> favoritesList = findByCategoryId(category.getId());
                favoritesList.forEach(f -> {
                    Password password = passwordService.findByFavoritesId(f.getId());
                    f.setPassword(password);
                });
                category.setFavorites(favoritesList);
            });
        }
        // 查询用户瞬间
        if (PublicConstants.EXPORT_MOMENT_CODE.equals(moment)) {
            momentList = momentService.findByUserId(user.getId());
        }
        // 查询用户未完成的日程
        if (PublicConstants.EXPORT_TASK_CODE.equals(task)) {
            taskList = taskService.findUndoTaskByUserId(user.getId());
        }
        // 查询用户搜索引擎
        if (PublicConstants.EXPORT_SEARCH_CODE.equals(search)) {
            searchTypeList = searchTypeRepository.findByUserId(user.getId());
        }
        // 查询快捷导航
        if (PublicConstants.EXPORT_QUICK_NAVIGATION.equals(navigation)) {
            quickNavigationList = quickNavigationRepository.findAllByUserIdOrderBySort(user.getId());
        }
        // 查询备忘录
        if (PublicConstants.EXPORT_QUICK_NAVIGATION.equals(memorandum)) {
            memorandumList = memorandumRepository.findByUserId(user.getId());
        }

        // 写入输出流
        writeXML(out, categories, momentList, taskList, searchTypeList, quickNavigationList, memorandumList);
    }

    private void writeXML(OutputStream out, List<Category> categories, List<Moment> momentList, List<Task> taskList, List<SearchType> searchTypeList, List<QuickNavigation> quickNavigationList, List<Memorandum> memorandumList) throws IOException {
        Document document = DocumentHelper.createDocument();
        Element root = document.addElement("DATA");
        if (!CollectionUtils.isEmpty(categories)) {
            Element categoriesList = root.addElement("CATEGORIES");
            categories.forEach(c -> {
                Element category = categoriesList.addElement("CATEGORY");
                category.addElement("NAME").setText(c.getName());
                if (c.getSort() != null) {
                    category.addElement("SORT").setText(String.valueOf(c.getSort()));
                }
                if (PublicConstants.BOOKMARK_STYLE_CODE.equals(c.getBookmark())) {
                    category.addElement("BOOKMARK").setText("true");
                }
                Element list = category.addElement("LIST");
                Optional.ofNullable(c.getFavorites()).orElse(Collections.emptyList()).forEach(f -> {
                    Element favorites = list.addElement("FAVORITES");
                    favorites.addElement("NAME").setText(f.getName());
                    favorites.addElement("URL").setText(f.getUrl());
                    favorites.addElement("ICON").setText(f.getIcon());
                    if (f.getSort() != null) {
                        favorites.addElement("SORT").setText(String.valueOf(f.getSort()));
                    }
                    if (PublicConstants.FAVORITES_STAR_CODE.equals(f.getStar())) {
                        favorites.addElement("STAR").setText("true");
                    }
                    if (PublicConstants.SHARE_CODE.equals(f.getIsShare())) {
                        favorites.addElement("SHARE").setText("true");
                    }
                    if (StrUtil.isNotBlank(f.getShortcut())) {
                        favorites.addElement("SHORTCUT").setText(f.getShortcut());
                    }
                    if (StrUtil.isNotBlank(f.getSchemaName())) {
                        favorites.addElement("SCHEMA_NAME").setText(f.getSchemaName());
                    }
                    if (f.getPassword() != null) {
                        Password password = f.getPassword();
                        Element pwd = favorites.addElement("USER");
                        pwd.addElement("ACCOUNT").setText(password.getAccount());
                        pwd.addElement("PASSWORD").setText(password.getPassword());
                    }
                });
            });
        }
        if (!CollectionUtils.isEmpty(momentList)) {
            Element moments = root.addElement("MOMENTS");
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            momentList.forEach(m -> {
                Element moment = moments.addElement("MOMENT");
                moment.addElement("CONTENT").setText(m.getContent());
                if (StrUtil.isNotBlank(m.getText())) {
                    moment.addElement("TEXT").setText(m.getText());
                }
                moment.addElement("TIME").setText(sdf.format(m.getCreateTime()));
            });
        }
        if (!CollectionUtils.isEmpty(searchTypeList)) {
            Element searchTypes = root.addElement("SEARCH_TYPES");
            searchTypeList.forEach(s -> {
                Element searchType = searchTypes.addElement("SEARCH_TYPE");
                searchType.addElement("NAME").setText(s.getName());
                searchType.addElement("URL").setText(s.getUrl());
                searchType.addElement("ICON").setText(s.getIcon());
            });
        }
        if (!CollectionUtils.isEmpty(taskList)) {
            Element tasks = root.addElement("TASKS");
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATE_PATTERN);
            SimpleDateFormat sdf1 = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            taskList.forEach(t -> {
                Element task = tasks.addElement("TASK");
                task.addElement("CONTENT").setText(t.getContent());
                task.addElement("DATE").setText(sdf.format(t.getTaskDate()));
                task.addElement("LEVEL").setText(String.valueOf(t.getLevel()));
                if (PublicConstants.TASK_ALARM_CODE.equals(t.getIsAlarm())) {
                    task.addElement("ALARM").setText("true");
                    task.addElement("TIME").setText(sdf1.format(t.getAlarmTime()));
                }
            });
        }
        if (!CollectionUtils.isEmpty(quickNavigationList)) {
            Element navigations = root.addElement("NAVIGATIONS");
            quickNavigationList.forEach(n -> {
                Element navigation = navigations.addElement("NAVIGATION");
                navigation.addElement("NAME").setText(n.getName());
                navigation.addElement("URL").setText(n.getUrl());
                navigation.addElement("ICON").setText(n.getIcon());
                if (n.getSort() != null) {
                    navigation.addElement("SORT").setText(String.valueOf(n.getSort()));
                }
            });
        }
        if (!CollectionUtils.isEmpty(memorandumList)) {
            Element memorandums = root.addElement("MEMORANDUMS");
            SimpleDateFormat sdf = new SimpleDateFormat(PublicConstants.FORMAT_DATETIME_PATTERN);
            memorandumList.forEach(r -> {
                Element memorandum = memorandums.addElement("MEMORANDUM");
                memorandum.addElement("CONTENT").setText(r.getContent());
                memorandum.addElement("CREATE_TIME").setText(sdf.format(r.getCreateTime()));
            });
        }
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding(StandardCharsets.UTF_8.name());
        XMLWriter writer = new XMLWriter(out, format);
        writer.setEscapeText(true);
        writer.write(document);
        writer.close();
    }

}
